/** @file    BigBrotherBundle.cpp
 *  @author  Alessandro Polo
 *  @version $Id: BigBrotherBundle.cpp 2821 2010-08-01 11:44:26Z alex $
 *  @brief
 * File containing methods for the wosh::services::BigBrotherBundle class.
 * The header for this class can be found in BigBrotherBundle.h, check that file
 * for class description.
 ****************************************************************************/
/* Copyright (c) 2007-2010, WOSH - Wide Open Smart Home 
 * by Alessandro Polo - OpenSmartHome.com
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the OpenSmartHome.com WOSH nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Alessandro Polo ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Alessandro Polo BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ****************************************************************************/

 #include "BigBrotherBundle.h"

 #include "BigBrotherImpl.h"
 #include "BigBrotherParser.h"

 #include <core/ObjectAllocator.h>
 #include <core/Fact.h>
 #include <core/List.h>
 #include <core/Table.h>

 #include <core/MessageProcessingCommon.h>
 #include <core/SystemError.h>
 #include <core/WoshKernel.h>

 #include <framework/communication/AudioMessage.h>
 #include <interfaces/services/BuildingManager.h>

//TEMP
#include <framework/building/Door.h>
 #include <framework/building/Room.h>
 #include <framework/building/Location.h>
 #include <interfaces/Device.h>
 #include <interfaces/devices/SensorMotion.h>
 #include <interfaces/devices/SensorLight.h>
 #include <interfaces/services/BuildingManager.h>
//TEMP

using namespace std;
using namespace wosh::communication;
namespace wosh {
 namespace services {

 WOSH_REGISTER(wosh::services::BigBrotherBundle, "wosh::interfaces::services::BigBrother", _BigBrother_VERSION, _static_BigBrotherBundle1 )
 WOSH_REGISTER(wosh::services::BigBrotherBundle, "wosh::BundleGeneric", _BigBrother_VERSION, _static_BigBrotherBundle2 )

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS

BigBrotherBundle::BigBrotherBundle()
	: BundleGeneric() {
	BundleGeneric::setName( _BigBrother_NAME, false );
	Properties.update( _KEY_Version, _BigBrother_VERSION );

//Log.setLevel(LOG_VERBOSE);

	Log(LOG_DEBUG, " Configuring workers.." );
	this->butlerWorker = new BigBrotherImpl(*this);
	this->butlerWorker->setListener(this);

	this->butlerParser = new BigBrotherParser(*this);
	this->butlerParser->setLocations( &this->butlerWorker->getLocations() );
	this->butlerParser->setTimeOut( 0 );

	Log(LOG_DEBUG, " Setting default properties and permissions.." );
//	Properties.set( _CommunicationManager_KEY_ServiceTimeOut, this->commParser->getTimeOut(), Permission( Permission::RX, Permission::Read, Permission::Read ) );

	Log(LOG_DEBUG, " Registering methods.." );
	MethodRequest* mmListHomeLocations = Methods.create<MethodRequest>( _METHOD_List, "List HomeLocations" );
	mmListHomeLocations->setMethod( this, (MethodRequestPtr)&BigBrotherBundle::mmDoListHomeLocations );
	mmListHomeLocations->getPermission().setMask( Permission::RX, Permission::RX, Permission::RX );

	MethodWRESULT* mmUpdateHomeLocations = Methods.create<MethodWRESULT>( _BigBrother_METHOD_update, "force update HomeLocations" );
	mmUpdateHomeLocations->setMethod( this, (MethodWRESULTPtr)&BigBrotherBundle::updateHomeLocations );
	mmUpdateHomeLocations->getPermission().setMask( Permission::RX, Permission::Read, Permission::Read );

	MethodMessageResponse* mmForceUserIn = Methods.create<MethodMessageResponse>( _UserManager_METHOD_forcelocation, "force user in a location" );
	mmForceUserIn->setMethod( this, (MethodMessageResponsePtr)&BigBrotherBundle::mmDoForceUserIn );
	mmForceUserIn->getPermission().setMask( Permission::RX, Permission::Read, Permission::Read );

	setBundleState(Bundle::STATE_CREATED, false);
 }


BigBrotherBundle::~BigBrotherBundle() {
	Log(LOG_INFO, " Destroying.." );
	if ( isBundleRunning() ) {
		Log(LOG_WARNING, "~BigBrotherBundle() : Destroying while Bundle is running! Trying to stop.." );
		bundleStop();
	 }
	delete this->butlerWorker; this->butlerWorker = NULL;
	delete this->butlerParser; this->butlerParser = NULL;
	Log(LOG_VERBOSE, ":~BigBrotherBundle() : Destroyed." );
 }

//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////// BUNDLE CONTROL

WRESULT BigBrotherBundle::bundleStart() {
	if ( !BundleGeneric::bundleValidate_StartStop(Bundle::STATE_STARTING) ) return WRET_ERR_WRONG_STATE;

	WRESULT ret = BundleGeneric::start_SynchThread( this->butlerParser );

	BusDevices.setMessageHandler(this);
	WRESULT bus_d_connected = BusDevices.connect( _Bus_Devices );
	if ( WFAILED(bus_d_connected) )
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "bundleStart", "FAILED#%d Connecting Bus ("_Bus_Devices")..", bus_d_connected );

	BusSecurity.setMessageHandler(this);
	WRESULT bus_s_connected = BusSecurity.connect( _Bus_Security );
	if ( WFAILED(bus_s_connected) )
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "bundleStart", "FAILED#%d Connecting Bus ("_Bus_Security")..", bus_s_connected );

	BusBuilding.setMessageHandler(this);
	WRESULT bus_b_connected = BusBuilding.connect( _Bus_Building );
	if ( WFAILED(bus_b_connected) )
		SystemError::raise(this, Log, SystemError::WARNING, 0, "bundleStart", "FAILED#%d Connecting Bus (wosh.Bus.Building)..", bus_b_connected );

	ret = BundleGeneric::start_SynchThread( this->butlerWorker );
	// BUNDLE-STATE (STARTED) will be updated async by WORKER, through call: thread_event()

	updateHomeLocations();

	return ret;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT BigBrotherBundle::bundleStop() {
	if ( !BundleGeneric::bundleValidate_StartStop(Bundle::STATE_STOPPING) ) return WRET_ERR_WRONG_STATE;

	WRESULT ret = BundleGeneric::stop_SynchThread( this->butlerParser );

	WRESULT bus_d_disconnected = BusDevices.disconnect();
	if ( WFAILED(bus_d_disconnected) )
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "bundleStart", "FAILED#%d Disconnecting Bus ("_Bus_Devices")..", bus_d_disconnected );

	WRESULT bus_b_disconnected = BusBuilding.disconnect();
	if ( WFAILED(bus_b_disconnected) )
		SystemError::raise(this, Log, SystemError::WARNING, 0, "bundleStart", "FAILED#%d Disconnecting Bus (wosh.Bus.Building)..", bus_b_disconnected );

	WRESULT bus_s_disconnected = BusSecurity.disconnect();
	if ( WFAILED(bus_s_disconnected) )
		SystemError::raise(this, Log, SystemError::WARNING, 0, "bundleStart", "FAILED#%d Disconnecting Bus ("_Bus_Security")..", bus_s_disconnected );

	ret = BundleGeneric::stop_SynchThread( this->butlerWorker );
	// BUNDLE-STATE (STOPPED) will be updated async by WORKER, through call: thread_event()

	return ret;
 }

////////////////////////////////////////////////////////////////////////////////////////////// BUNDLE CONTROL
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////// PROCESS BUS MESSAGES

void BigBrotherBundle::busMessage( const Message& message, const Bus* source ) {
	if ( message.isEmpty() ) return;
	// avoid double-response

	if ( message.getSourceBus() == BusCore.getBusName() ) {
		if ( message.getCurrentBus() == BusBuilding.getBusName() )
			return;
		if ( message.getCurrentBus() == BusDevices.getBusName() )
			return;
		if ( message.getCurrentBus() == BusSecurity.getBusName() )
			return;
	 }
	else if ( message.getSourceBus() == BusDevices.getBusName() ) {
		if ( message.getCurrentBus() == BusBuilding.getBusName() )
			return;
		if ( message.getCurrentBus() == BusCore.getBusName() )
			return;
		if ( message.getCurrentBus() == BusSecurity.getBusName() )
			return;
	 }
	else if ( message.getSourceBus() == BusSecurity.getBusName() ) {
		if ( message.getCurrentBus() == BusDevices.getBusName() )
			return;
		if ( message.getCurrentBus() == BusCore.getBusName() )
			return;
		if ( message.getCurrentBus() == BusBuilding.getBusName() )
			return;
	 }

	this->butlerWorker->evalMessageIncoming( message );

	if ( !MessageFilter::isMessageTarget(message, this) )
		return;

	this->butlerParser->evalMessage( message );

	BundleGeneric::busMessage(message, source);
 }

//////////////////////////////////////////////////////////////////////////////////////// PROCESS BUS MESSAGES
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////// GENERATED EVENTS


//////////////////////////////////////////////////////////////////////////////////////////// GENERATED EVENTS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

void BigBrotherBundle::updateObject( MessageContent* content, const string& uri, BigBrotherImpl* )
 {
	Message* message = new Message(content);
	message->setSource(this);
	message->getDestination().fromString(uri);

	if ( uri.find("Building") != string::npos ) {
		BusCore.postMessage(message);
		message->setDestinationBus( _Bus_Building );
	 }
//	else if ( uri.find("Device") != string::npos ) {
//		BusDevices.postMessage(message);
//		message->setDestinationBus( _Bus_Devices );
//	 }
	else
		BusCore.postMessage(message);
 }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////// PROPERTY EVENT


////////////////////////////////////////////////////////////////////////////////////////////// PROPERTY EVENT
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////// METHODS

WRESULT BigBrotherBundle::updateHomeLocations()		{ return this->butlerParser->updateHomeLocations(); }

Response* BigBrotherBundle::mmDoListHomeLocations( const Request* request )
 {
	if ( request == NULL ) return NULL;
	string option = request->getArgument().getData();
	Log(LOG_VERBOSE, ":mmDoListMessages(%s)", option.c_str() );

	if ( option == "s" || option == "short" ) {
		List* objs =  new List();
		this->butlerWorker->getLocations().mutex().lockForRead();
		std::map<string, Location*>::const_iterator it;
		std::map<string, Location*>::const_iterator it_end = this->butlerWorker->getLocations().stdEnd();
		for ( it=this->butlerWorker->getLocations().stdBegin(); it!=it_end; it++ ) {
			objs->add( new Data(it->first) );
		 }
		this->butlerWorker->getLocations().mutex().unLock();
		return request->replyResponse(WRET_OK, objs );
	 }
	else {
		this->butlerWorker->getLocations().mutex().lockForRead();
		Table* tbl = new Table( this->butlerWorker->getLocations().size_(), 7, true );
		tbl->setTableName("Locations");
		tbl->setHeaderHorizontal( new Data("Name"), 0);
		tbl->setHeaderHorizontal( new Data("Reference"), 1);
		tbl->setHeaderHorizontal( new Data("Parent"), 2);
		tbl->setHeaderHorizontal( new Data("Access"), 3);
		tbl->setHeaderHorizontal( new Data("Children"), 4);
		tbl->setHeaderHorizontal( new Data("Devices"), 5);
		tbl->setHeaderHorizontal( new Data("Users"), 6);
		unsigned int iRow = 0;
		std::map<string, Location*>::const_iterator it;
		std::map<string, Location*>::const_iterator it_end = this->butlerWorker->getLocations().stdEnd();
		for ( it=this->butlerWorker->getLocations().stdBegin(); it!=it_end; it++ ) {
			if ( it->second == NULL ) continue;
			tbl->set( new Data(it->second->getName()), iRow, 0);
			tbl->set( new Data(it->second->getURI().toString()), iRow, 1);
			tbl->set( new Data(it->second->getParentName()), iRow, 2);
			tbl->set( new Data(it->second->getAccessTo().size()), iRow, 3);
			tbl->set( new Data(Utilities::joinVector(it->second->getChildren(),";")), iRow, 4);
			tbl->set( new Data(Utilities::joinVector(it->second->getDevicesURI(),";")), iRow, 5);
			tbl->set( new Data(Utilities::joinVector(it->second->getUsersName(),";")), iRow, 6);
			++iRow;
		 }
		this->butlerWorker->getLocations().mutex().unLock();
		return request->replyResponse(WRET_OK, tbl );
	 }
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

Response* BigBrotherBundle::mmDoForceUserIn( const Message* requestMessage )
 {
	if ( requestMessage == NULL || requestMessage->isEmpty() ) return NULL;
	if ( !requestMessage->getContent()->isRequest() ) return NULL;
	const Request* request = requestMessage->getContent()->asRequest();
	Data dUsername;
	Data dLocation;
	request->extractArguments(dUsername, dLocation);
	if ( dUsername.isEmpty() || dLocation.isEmpty() ) {
		if ( request->doOmitResponse() )
			return NULL;
		return request->replyResponse(WRET_ERR_PARAM, "Invalid arguments [username, location]" );
	 }

	std::string sender = "";
	if ( requestMessage->getSecurityToken() != NULL )
		sender = requestMessage->getSecurityToken()->getUser();

	Log(LOG_INFO, "mmDoForceUserIn() : Force %s in %s", dUsername.getData().c_str(), dLocation.getData().c_str() );

	this->butlerWorker->setUserLocation(dUsername.getData(), dLocation.getData(), 1.0);

	return request->replyResponse(WRET_OK);
 }

///////////////////////////////////////////////////////////////////////////////////////////////////// METHODS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

 }; // namespace services
}; // namespace wosh
